#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod dissassemble;
mod memory;
mod peripherals;
mod tool_box;
mod utils;

use anyhow::{anyhow, Result};
use arboard;
use dissassemble::*;
use mcu_debug_probe_ui::*;
use memory::*;
use peripherals::*;
use probe_rs::debug::{DebugInfo, SteppingMode};
use probe_rs::CoreStatus;
use probe_rs::{
    probe::list::Lister, Core, InstructionSet, MemoryInterface, Permissions, RegisterValue, Session,
};
use slint::{CloseRequestResponse, Model, Timer, TimerMode, VecModel};
use svd_parser::{
    svd::{Device, MaybeArray, Name},
    Config,
};
use tool_box::*;

use std::{
    fs::{self, File},
    io::{Read, Write},
    ops::Range,
    rc::Rc,
    sync::{Arc, Mutex},
    time::Duration,
};

// use tracing_subscriber::{
//     fmt, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer,
// };

struct App {
    ui_weak: slint::Weak<AppWindow>,
    session: Option<Session>,
    flash_range: Option<Range<u64>>,
    dissassemble_lines: Option<(Vec<DissassembleLine>, Range<usize>)>,
    debug_info: Option<Rc<DebugInfo>>,
    running_timer: Option<Timer>,
}

impl App {
    fn new() -> Self {
        Self {
            ui_weak: slint::Weak::default(),
            session: None,
            flash_range: None,
            dissassemble_lines: None,
            debug_info: None,
            running_timer: Some(Timer::default()),
        }
    }

    fn ui(&self) -> AppWindow {
        self.ui_weak.unwrap()
    }

    fn _save_state(&self) -> Result<()> {
        Ok(())
    }
}

fn get_mcu_core(app: &mut App) -> Option<(Core, bool)> {
    if let Some(session) = &mut app.session {
        match session.core(0) {
            Ok(mut core) => match core.core_halted() {
                Ok(halted) => {
                    return Some((core, halted));
                }
                Err(e) => {
                    println!("get core halt status err: {:?}", e);
                }
            },
            Err(e) => {
                println!("get core from session err: {:?}", e);
            }
        }
    }

    return None;
}

fn initialize_ui(app: &mut App) -> Result<AppWindow, slint::PlatformError> {
    let ui = AppWindow::new()?;
    app.ui_weak = ui.as_weak();

    let ui_io = ui.global::<UiIo>();

    reflash_probe_list(&ui_io);

    Ok(ui)
}

fn main() -> Result<()> {
    let mut app = App::new();

    let ui = initialize_ui(&mut app).unwrap();

    const ARMV6M: &[u8] = include_bytes!("cores/armv6m.yaml");
    probe_rs::config::add_target_from_yaml(ARMV6M)?;
    const ARMV7M: &[u8] = include_bytes!("cores/armv7m.yaml");
    probe_rs::config::add_target_from_yaml(ARMV7M)?;
    const ARMV8M: &[u8] = include_bytes!("cores/armv8m.yaml");
    probe_rs::config::add_target_from_yaml(ARMV8M)?;
    const RISCV: &[u8] = include_bytes!("cores/riscv.yaml");
    probe_rs::config::add_target_from_yaml(RISCV)?;

    // tracing_subscriber::registry()
    //     .with(fmt::layer())
    //     .init();

    let app = Arc::new(Mutex::new(app));
    let ui_io = ui.global::<UiIo>();

    ui_io.on_open_url(|url| {
        open::that(url.as_str()).ok();
    });

    ui_io.on_copy_to_clipboard({
        let app = app.clone();
        move |text| {
            let ui = app.lock().unwrap().ui();
            let ui_io = ui.global::<UiIo>();
            let tip = format!("Copy: {}", text);
            if let Ok(mut clip) = arboard::Clipboard::new() {
                clip.set_text(text.to_string()).ok();
                ui_io.set_help_tip(tip.into());
            } else {
                ui_io.set_help_tip("Copy fail".into());
            }
        }
    });

    ui_io.on_probe_toolbox({
        let app = app.clone();
        move |cmd| {
            let app_run = app.clone();
            let mut app = app.lock().unwrap();
            let ui = app.ui();
            let ui_io = ui.global::<UiIo>();
            match cmd {
                CmdToolbox::Reflash => {
                    reflash_probe_list(&ui_io);
                }
                CmdToolbox::Attach => {
                    if let Some(tim) = &app.running_timer {
                        tim.stop();
                    }

                    app.flash_range = None;
                    if let Some(_) = app.session {
                        app.session = None;
                        ui_io.set_help_tip("Session droped".into());
                        ui_io.set_attach_state(false);
                    } else {
                        if let Some(session) = probe_attach(&ui_io) {
                            app.session = Some(session);
                            ui_io.set_help_tip("Attach success".into());
                            ui_io.set_attach_state(true);
                        } else {
                            //ui_io.set_help_tip("Attach FAIL".into());
                            ui_io.set_attach_state(false);
                        }
                        ui_io.set_dissassemble_line(Rc::new(VecModel::from(Vec::new())).into());
                    }
                }
                CmdToolbox::Halt => {
                    if let Some(tim) = &app.running_timer {
                        tim.stop();
                    }
                    //let flash_range = app.flash_range.clone();
                    let mut core = if let Some((core, _)) = get_mcu_core(&mut app) {
                        core
                    } else {
                        ui_io.set_help_tip("core halt error".into());
                        return;
                    };

                    if let Ok(core_info) = core.halt(Duration::from_millis(500)) {
                        update_mcu_core(&mut core, &ui_io, core_info.pc);
                        drop(core);
                        make_pc_line_visible(&mut app, &ui_io, core_info.pc, 50);
                        // if update_core_and_dissassemble(&mut core, &ui_io, cpu_info.pc, &flash_range) {
                        //     ui_io.set_help_tip("Halted".into());
                        // }
                    }
                }
                CmdToolbox::Reset => {
                    if let Some(tim) = &app.running_timer {
                        tim.stop();
                    }
                    //let flash_range = app.flash_range.clone();
                    let mut core = if let Some((core, _)) = get_mcu_core(&mut app) {
                        core
                    } else {
                        ui_io.set_help_tip("core not halted".into());
                        return;
                    };

                    match core.reset_and_halt(Duration::from_millis(500)) {
                        Ok(core_info) => {
                            update_mcu_core(&mut core, &ui_io, core_info.pc);
                            drop(core);
                            make_pc_line_visible(&mut app, &ui_io, core_info.pc, 50);
                            // if update_core_and_dissassemble(&mut core, &ui_io, core_info.pc, &flash_range) {
                            //     ui_io.set_help_tip("Reset OK".into());
                            // }
                        }
                        Err(e) => {
                            ui_io.set_help_tip(format!("Reset ERR: {:?}", e).into());
                        }
                    }
                }
                CmdToolbox::Run => {
                    if let Some(_) = app.session {
                        let mut core = if let Some((core, halted)) = get_mcu_core(&mut app) {
                            if !halted {
                                ui_io.set_help_tip("Aleady running".into());
                                return;
                            }
                            core
                        } else {
                            ui_io.set_help_tip("core not halted".into());
                            return;
                        };
                        if let Err(e) = core.run() {
                            ui_io.set_help_tip(format!("Run ERR: {:?}", e).into());
                        } else {
                            ui_io.set_help_tip("Running".into());
                            drop(core);

                            if let Some(tim) = &app.running_timer {
                                tim.start(
                                    TimerMode::Repeated,
                                    std::time::Duration::from_millis(1000),
                                    move || {
                                        //println!("This will be printed every 1000ms.");
                                        let mut pc = 0;
                                        if let Ok(mut app) = app_run.try_lock() {
                                            let ui = app.ui();
                                            let ui_io = ui.global::<UiIo>();
                                            if let Some(session) = &mut app.session {
                                                match session.core(0) {
                                                    Ok(mut core) => {
                                                        match core.status() {
                                                            Ok(status) => {
                                                                //println!("{:?}", status);
                                                                if let CoreStatus::Halted(_reason) =
                                                                    status
                                                                {
                                                                    let pc_reg =
                                                                        core.program_counter().id();
                                                                    match core.read_core_reg::<u32>(
                                                                        pc_reg,
                                                                    ) {
                                                                        Ok(value) => {
                                                                            pc = value as u32
                                                                        }
                                                                        Err(e) => {
                                                                            println!(
                                                                                "read pc err: {:?}",
                                                                                e
                                                                            );
                                                                        }
                                                                    }

                                                                    update_mcu_core(
                                                                        &mut core,
                                                                        &ui_io,
                                                                        pc.into(),
                                                                    );
                                                                }
                                                            }
                                                            Err(e) => {
                                                                println!(
                                                                    "get core status err: {:?}",
                                                                    e
                                                                );
                                                            }
                                                        }
                                                    }
                                                    Err(e) => {
                                                        println!(
                                                            "get core from session err: {:?}",
                                                            e
                                                        );
                                                    }
                                                }
                                            }
                                            if pc != 0 {
                                                if let Some(tim) = &app.running_timer {
                                                    tim.stop();
                                                }

                                                make_pc_line_visible(
                                                    &mut app,
                                                    &ui_io,
                                                    pc.into(),
                                                    50,
                                                );
                                                ui_io.set_help_tip("Stop running".into());
                                                //println!("stop running");
                                                //make_pc_line_visible(&mut app, &ui_io, pc, 50)
                                            }
                                        }
                                    },
                                );
                            }
                        }
                    }
                }
                CmdToolbox::Step => {
                    let mut core = if let Some((core, halted)) = get_mcu_core(&mut app) {
                        if !halted {
                            ui_io.set_help_tip("Not halt".into());
                            return;
                        }
                        core
                    } else {
                        ui_io.set_help_tip("core not halted".into());
                        return;
                    };

                    match core.step() {
                        Ok(core_info) => {
                            update_mcu_core(&mut core, &ui_io, core_info.pc);
                            drop(core);
                            make_pc_line_visible(&mut app, &ui_io, core_info.pc, 50);
                            // if update_core_and_dissassemble(&mut core, &ui_io, core_info.pc, &flash_range) {
                            //     ui_io.set_help_tip("Step OK".into());
                            // }
                        }
                        Err(e) => {
                            ui_io.set_help_tip(format!("Reset ERR: {:?}", e).into());
                        }
                    }
                }
                CmdToolbox::StepInto | CmdToolbox::StepOver | CmdToolbox::StepOut => {
                    let mode = match cmd {
                        CmdToolbox::StepInto => SteppingMode::IntoStatement,
                        CmdToolbox::StepOver => SteppingMode::OverStatement,
                        CmdToolbox::StepOut => SteppingMode::OutOfStatement,
                        _ => SteppingMode::IntoStatement,
                    };

                    let mut app_debug_info = None;
                    if let Some(debug_info) = &app.debug_info {
                        app_debug_info = Some(debug_info.clone());
                    }
                    let mut core = if let Some((core, halted)) = get_mcu_core(&mut app) {
                        if !halted {
                            ui_io.set_help_tip("Not halt".into());
                            return;
                        }
                        core
                    } else {
                        ui_io.set_help_tip("core not halted".into());
                        return;
                    };

                    if let Some(debug_info) = app_debug_info {
                        match mode.step(&mut core, &debug_info) {
                            Ok((status, pc)) => {
                                if let CoreStatus::Halted(_r) = status {
                                    update_mcu_core(&mut core, &ui_io, pc);
                                    drop(core);
                                    make_pc_line_visible(&mut app, &ui_io, pc, 50);
                                }
                            }
                            Err(e) => {
                                ui_io.set_help_tip(format!("Setp ERR: {:?}", e).into());
                            }
                        }
                    }
                }
                CmdToolbox::LoadELF => {
                    let dialog = rfd::FileDialog::new()
                        .set_title("Select a ELF")
                        .add_filter("ELF File", &["elf"])
                        .add_filter("ALL File", &["*"]);

                    if let Some(path_op) = dialog.pick_file() {
                        app.debug_info = None;
                        ui_io.set_debug_info_valid(false);
                        match DebugInfo::from_file(path_op.clone()) {
                            Ok(dbg_info) => {
                                app.debug_info = Some(Rc::new(dbg_info));
                                ui_io.set_debug_info_valid(true);
                            }
                            Err(e) => {
                                ui_io.set_help_tip(format!("Load ELF ERR: {:?}", e).into());
                            }
                        }
                    }
                }
            }
        }
    });

    ui_io.on_probe_memory({
        let app = app.clone();
        move |idx, cmd| {
            let mut app = app.lock().unwrap();
            let ui = app.ui();
            let ui_io = ui.global::<UiIo>();

            if let Some(session) = &mut app.session {
                if memory_process(idx as usize, cmd, session, &ui_io) {
                    ui_io.set_help_tip("OK".into());
                }
            }
        }
    });

    ui_io.on_probe_peripherals({
        let app = app.clone();
        move |cmd| {
            let app = app.lock().unwrap();
            let ui = app.ui();
            let ui_io = ui.global::<UiIo>();

            match cmd {
                CmdPeripherals::LoadSVD => {
                    if let Some((device, path)) = parse_svd_file() {
                        let peripherals = peripherals_generate(device);
                        ui_io.set_svd_file_path(path.into());
                        ui_io.set_peripherals(Rc::new(VecModel::from(peripherals)).into())
                    }
                }
            }
        }
    });

    ui_io.on_probe_peripheral_get({
        let app = app.clone();
        move |p_name, r_name| {
            let mut app = app.lock().unwrap();
            let ui = app.ui();
            let ui_io = ui.global::<UiIo>();

            let core = if let Some((core, halted)) = get_mcu_core(&mut app) {
                if !halted {
                    ui_io.set_help_tip("Not halt".into());
                    return;
                }
                core
            } else {
                ui_io.set_help_tip("core not halted".into());
                return;
            };

            peripheral_get_data(ui_io, core, p_name.to_string(), r_name.to_string());
        }
    });

    ui_io.on_probe_peripheral_modify({
        let app = app.clone();
        move |data, p_name, r_name, bit_offset, bit_width| {
            let (mask, value) =
                if let Some((m, v)) = get_mask_and_value(data.to_string(), bit_offset, bit_width) {
                    (m, v)
                } else {
                    return;
                };

            let mut app = app.lock().unwrap();
            let ui = app.ui();
            let ui_io = ui.global::<UiIo>();

            let core = if let Some((core, halted)) = get_mcu_core(&mut app) {
                if !halted {
                    ui_io.set_help_tip("Not halt".into());
                    return;
                }
                core
            } else {
                ui_io.set_help_tip("core not halted".into());
                return;
            };
            peripheral_modify_data(
                ui_io,
                core,
                p_name.to_string(),
                r_name.to_string(),
                mask,
                value,
            );
        }
    });

    ui_io.on_probe_disassemble({
        let app = app.clone();
        move |cmd, start_str, size_str| {
            let mut app = app.lock().unwrap();
            let ui = app.ui();
            let ui_io = ui.global::<UiIo>();

            if cmd == CmdDissassemble::Export {
                if let Some((diss_asm_list, _)) = &app.dissassemble_lines {
                    let dialog = rfd::FileDialog::new()
                        .set_title("Export ASM to file >> >> >> NOTE: Press 'Alt' if 'OVER WRITE' confirm message box not POP UP")
                        .set_file_name("mcu_debug_probe.S")
                        .add_filter("ASM File", &["s", "S"]);

                    if let Some(path_op) = dialog.save_file() {
                        let result = fs::OpenOptions::new()
                            .write(true)
                            .create(true)
                            .append(false)
                            .open(path_op);
                        if let Ok(mut file) = result {
                            for line in diss_asm_list {
                                let mut l = line.assemble.to_string();
                                l.push_str("\r\n");
                                file.write(l.as_bytes()).ok();
                            }
                        }
                    }
                }

                return;
            }

            let mut core = if let Some((core, halted)) = get_mcu_core(&mut app) {
                if !halted {
                    ui_io.set_help_tip("Not halt".into());
                    return;
                }
                core
            } else {
                ui_io.set_help_tip("core not halted".into());
                return;
            };

            let addr = if let Some(num) = utils::parse_number_str(&start_str) {
                num as u64
            } else {
                ui_io.set_help_tip("address can't parse".into());
                return;
            };

            let size = if let Some(num) = utils::parse_number_str(&size_str) {
                num as usize
            } else {
                ui_io.set_help_tip("size can't parse".into());
                return;
            };

            match code_dissassemble(&mut core, addr, size) {
                Ok(diss_asm_list) => {
                    drop(core);

                    ui_io.set_help_tip("Disassemble success".into());

                    let mut diss_asm_slice: Vec<DissassembleLine> = Vec::new();
                    if diss_asm_list.len() > 100 {
                        diss_asm_slice.extend_from_slice(&diss_asm_list[..100]);
                    } else {
                        diss_asm_slice = diss_asm_list.clone();
                    }

                    let range = 0..diss_asm_slice.len();
                    ui_io.set_dissassemble_line(Rc::new(VecModel::from(diss_asm_slice)).into());

                    ui_io.set_assemble_line_number(diss_asm_list.len() as i32);
                    ui_io.set_assemble_line_current(0);
                    app.dissassemble_lines = Some((diss_asm_list, range));

                    let range = addr..addr + size as u64;
                    app.flash_range = Some(range);
                }
                Err(e) => {
                    ui_io.set_help_tip(format!("Dissassemble ERR: {:?}", e).into());
                }
            }
        }
    });

    ui_io.on_probe_assemble_scroll({
        let app = app.clone();
        move |scroll_dir, disp_lines_num, scroll_lines| {
            let disp_lines_num = disp_lines_num as usize;

            let mut app = app.lock().unwrap();
            let ui = app.ui();
            let ui_io = ui.global::<UiIo>();

            if scroll_lines < 0 {
                //jmp to pc
                let pc = (ui_io.get_PC() as u32) as u64;
                make_pc_line_visible(&mut app, &ui_io, pc, disp_lines_num);
                return;
            }

            if let Some((diss_asm, range)) = &mut app.dissassemble_lines {
                if scroll_dir == 0 {
                    //scrollbar move
                    let target_line = (scroll_lines & 0xFFFFFFF) as usize;
                    //println!("target_line {}  diss_asm.len() {} range.start {}", target_line, diss_asm.len(), range.start);
                    if target_line >= diss_asm.len() || target_line == range.start {
                        return;
                    }
                    range.start = target_line;
                    if range.start > diss_asm.len() - disp_lines_num {
                        range.start = diss_asm.len() - disp_lines_num;
                    }
                    range.end = range.start + disp_lines_num;
                } else {
                    let scroll_lines = (scroll_lines & 0xFFFFFFF) as usize;
                    let scroll_up = if scroll_dir > 0 { true } else { false };
                    if (scroll_up && range.start == 0)
                        || (!scroll_up && range.end >= diss_asm.len())
                    {
                        return;
                    }
                    if scroll_up {
                        let delta = if range.start >= scroll_lines {
                            scroll_lines
                        } else {
                            range.start
                        };
                        range.start -= delta;
                        range.end -= delta;
                        if range.end - range.start > disp_lines_num {
                            range.end = range.start + disp_lines_num;
                        }
                    } else {
                        let delta = if diss_asm.len() - range.end > scroll_lines {
                            scroll_lines
                        } else {
                            diss_asm.len() - range.end
                        };
                        range.start += delta;
                        range.end += delta;

                        if range.end - range.start > disp_lines_num {
                            range.start = range.end - disp_lines_num;
                        }
                    }
                }
                ui_io.set_assemble_line_current(range.start as i32);
                let mut diss_asm_slice: Vec<DissassembleLine> = Vec::new();
                diss_asm_slice.extend_from_slice(&diss_asm[range.start..range.end]);
                ui_io.set_dissassemble_line(Rc::new(VecModel::from(diss_asm_slice)).into());
            }
        }
    });

    ui_io.on_probe_set_breakpoint({
        let app = app.clone();
        move |set, addr| {
            let mut app = app.lock().unwrap();
            let ui = app.ui();
            let ui_io = ui.global::<UiIo>();

            let mut core = if let Some((core, halted)) = get_mcu_core(&mut app) {
                if !halted {
                    ui_io.set_help_tip("Not halt".into());
                    return;
                }
                core
            } else {
                ui_io.set_help_tip("core not halted".into());
                return;
            };

            if !set && addr == -1 {
                //clear all breakpoint
                if let Err(e) = core.clear_all_hw_breakpoints() {
                    ui_io.set_help_tip(format!("clear all breakpoint FAIL: {:?}", e).into());
                } else {
                    ui_io.set_help_tip(format!("clear all breakpoint").into());
                }
                drop(core);
                if let Some((diss_asm_list, _)) = &mut app.dissassemble_lines {
                    for line in diss_asm_list {
                        line.breakpoint = false;
                    }
                }
                return;
            }

            //set/clr one breakpoint
            let address = (addr as u32) as u64;

            if let Err(e) = if set {
                core.set_hw_breakpoint(address)
            } else {
                core.clear_hw_breakpoint(address)
            } {
                ui_io.set_help_tip(format!("set/clr breakpoint ERR: {:?}", e).into());
            } else {
                drop(core);
                if let Some((diss_asm_list, _)) = &mut app.dissassemble_lines {
                    for line in diss_asm_list {
                        if addr == line.address {
                            line.breakpoint = set;
                            break;
                        }
                    }
                }
                ui_io.set_help_tip(
                    format!(
                        "{} breakpoint at: 0x{:08X}",
                        if set { "Set" } else { "Clr" },
                        address
                    )
                    .into(),
                );
            }
        }
    });

    ui.window().on_close_requested(move || {
        let mut app = app.lock().unwrap();
        app.running_timer = None;
        CloseRequestResponse::HideWindow
    });

    ui.run().unwrap();

    Ok(())
}
